/**
 * 
 */
package Verifier.FiatShamirProofsVerification;

import Verifier.ArithmeticSystem.ArithmeticConvertor;
import Verifier.ArithmeticSystem.FieldElement;
import Verifier.ArithmeticSystem.GroupElement;
import Verifier.ArithmeticSystem.ProductElement;
import Verifier.ByteTree.ByteTree;
import Verifier.ByteTree.Leaf;
import Verifier.ByteTree.Node;

/**
 * The class executes Algorithm No.18 to verify the proof of Correct Decryption Factors.
 * 
 * @author Moshe Baavur
 *
 */
public class CorrectDecryptionFactorsProofVerifier extends ProofVerifier {
	GroupElement[] elementBDoubleTags;
	GroupElement elementYTag;
	GroupElement elementBTag;
	
	FieldElement elementK_x;
	
	ProductElement elementA;
	GroupElement elementB;
	
	/**
	 * Initializes the proof with the correct information.
	 * 
	 * @param info the proof info which has all the needed arguments initialized using the third init method.
	 */
	public CorrectDecryptionFactorsProofVerifier(FiatShamirProofInfo info) {
		super(info);
		
		this.elementBDoubleTags = null;
		this.elementYTag = null;
		this.elementBTag = null;
		
		this.elementK_x = null;
		
		this.elementA = null;
		this.elementB = null;
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#checkInputValidity()
	 */
	@Override
	protected boolean checkInputValidity() {
		// no checking is needed at the moment - the input parameters are controlled by us
		return (true);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretCommitment()
	 */
	@Override
	protected boolean interpretCommitment() {
		// interpret the commitment
		ByteTree commitmentTree = new ByteTree(proofInfo.proofCommitment());

		if (commitmentTree.getFirstByteTreeElement() instanceof Leaf) {
			return (false);
		}

		Node commitmentRoot = (Node) commitmentTree.getFirstByteTreeElement();
		// the number of children should be 2 representing: y', B''
		if (2 != commitmentRoot.getDataLength()) {
			return (false);
		}
		
		this.elementBDoubleTags = null;
		this.elementYTag = null;
		this.elementBTag = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			elementYTag = ArithmeticConvertor.ToGroupElement(
					new ByteTree(commitmentRoot.getChild(0)),
					proofInfo.worldGroup());
			elementBDoubleTags = ArithmeticConvertor.ToGroupElementArray(
					new ByteTree(commitmentRoot.getChild(1)),
					proofInfo.plaintextsGroup());
		} catch (Exception e) {
			return (false);
		}

		// now, check that the array is in the appropriate size
		if (1 != elementBDoubleTags.length) {
			return (false);
		}
		this.elementBTag = this.elementBDoubleTags[0];
		
		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretReply()
	 */
	@Override
	protected boolean interpretReply() {
		// interpret the reply
		ByteTree replyTree = new ByteTree(proofInfo.proofReply());

		if (replyTree.getFirstByteTreeElement() instanceof Node) {
			return (false);
		}

		this.elementK_x = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			this.elementK_x = ArithmeticConvertor.ToFieldElement(replyTree, proofInfo.worldField());
		} catch (Exception e) {
			return (false);
		}

		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#computeSeed()
	 */
	@Override
	protected void computeSeed() throws Exception {
		// compute a seed s using the RandomOracle
		Node seedGroupNode = new Node(null);
		Node leftNode = new Node(null);
		Node rightNode = new Node(null);

		try {
			ByteTree creatorByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.worldGroup().getGroupCreatorAsGroupElement());
			// noted as y - the partial public key
			ByteTree partialPublicKeyByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.partialPublicKey());
	
			// noted as w
			ByteTree inputCiphertextsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.inputCiphertext());
			// noted as f
			ByteTree decryptionFactorsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.decryptionFactors());

			leftNode.addElement(creatorByteTree.getFirstByteTreeElement());
			leftNode.addElement(inputCiphertextsByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(leftNode);
		
			rightNode.addElement(partialPublicKeyByteTree.getFirstByteTreeElement());
			rightNode.addElement(decryptionFactorsByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(rightNode);
		}catch (Exception e) {
			// all should work
			return;
		}
		
		// create the seed from the byte tree
		createSeedFromByteTree(new ByteTree(seedGroupNode));
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#createRandomData()
	 */
	@Override
	protected void createRandomData() {
		this.elementA = (ProductElement) calculateRandomNumber(proofInfo.inputCiphertext());
		this.elementB = (GroupElement) calculateRandomNumber(proofInfo.decryptionFactors());
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#validateCalculations()
	 */
	@Override
	protected boolean validateCalculations() {
		// save the group's creator - remember that this is the creator of the group - G_q
		GroupElement groupCreator = proofInfo.worldGroup().getGroupCreatorAsGroupElement();
		
		// try validating y related calculations
		try {
			if (!proofInfo.partialPublicKey().power(lastComputedChallenge).multiply(elementYTag).equals(groupCreator.power(elementK_x.getElement()))) {
				return (false);
			}
		}catch (Exception e) {
			// couldn't validate y - something went wrong
			return (false);
		}
		
		// try validating B and A related calculations
		try {
			GroupElement elementDecryptedA = elementA.getElement(0).power(elementK_x.getElement()); // this should be the decoding of the first element in A using K_x
			if (!elementB.power(lastComputedChallenge).multiply(elementBTag).equals(elementDecryptedA)) {
				return (false);
			}
		}catch (Exception e) {
			// couldn't validate B - something went wrong
			return (false);
		}
		
		// the proof was validated successfully
		return (true);
	}
}
